/*
 * Copyright (c) 2016 moondev.cn
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cn.moondev.moonlog.delegate.monitor;

import java.io.IOException;
import java.text.MessageFormat;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.google.common.base.Strings;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 
 * @author bosong
 *
 */
public class MonitorTimeFilter implements Filter {

    /** 日志. */
    // 用户记录所有日志
    private final Logger          perf                   = LoggerFactory.getLogger("performance");
    // 记录需要持久化的日志
    private final Logger          bizLogger                   = LoggerFactory.getLogger("monitor.biz");

    /** 阀值,0.5秒. */
    private static final int      THRESHOLD              = 500;

    /** 记录详细信息阀值20ms. */
    private static final int      LOG_DETAIL_THRESHOLD   = 20;

    private static final String[] STATIC_RESOUCES_SUFFIX = new String[] { ".js", ".css", ".png", ".jpg", ".gif" };

    /** FilterConfig. */
    private FilterConfig          config;

    /** 
     * @see javax.servlet.Filter#destroy()
     */
    @Override
    public void destroy() {
        //do nothing
    }

    /** 
     * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
     */
    public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain chain) throws IOException,
                                                                                      ServletException {
        HttpServletRequest request = (HttpServletRequest) arg0;
        HttpServletResponse response = (HttpServletResponse) arg1;

        String api = request.getRequestURI();
        //判定是否包含静态资源
        boolean result = containExtenSuffix(api);
        if (result) {
            chain.doFilter(request, response);
        } else {
            String requestString = dumpQueryString(request);
            Profiler.start("process http request");
            boolean error = Boolean.FALSE;
            try {
                chain.doFilter(request, response);
            } catch (Exception e) {
                perf.error("process http error,requestString=" + requestString, e);
                error = Boolean.TRUE;
            } finally {
                Profiler.release();

                String apicontext = config.getInitParameter("apicontext");
                if (!Strings.isNullOrEmpty(apicontext)) {
                    String[] apis = apicontext.split(",");
                    boolean existContext = false;
                    for (int i = 0; i < apis.length; i++) {
                        if (api.startsWith(apis[i])) {
                            existContext = true;
                            break;
                        }
                    }
                    if (!existContext) {
                        return;
                    }
                }

                long duration = Profiler.getDuration();
                String detail = Profiler.dump("Detail: ", "        ");
                if (error) {
                    perf.error(MessageFormat.format("|N|{0,number}|{1}: {2}\n{3}",
                        new Object[] { Long.valueOf(duration), requestString, "error", detail }));
                } else if (duration > THRESHOLD) {
                    perf.warn(MessageFormat.format("|Y|{0,number}|{1}\n{2}", new Object[] { Long.valueOf(duration),
                            requestString, detail }));
                    bizLogger.info(MessageFormat.format("|Y|{0,number}|{1}\n{2}", new Object[] { Long.valueOf(duration),
                            requestString, detail }));
                } else if (perf.isInfoEnabled() && duration >= LOG_DETAIL_THRESHOLD) {
                    perf.info(MessageFormat.format("|Y|{0,number}|{1}\n{2}", new Object[] { Long.valueOf(duration),
                            requestString, detail }));
                    bizLogger.info(MessageFormat.format("|Y|{0,number}|{1}\n{2}", new Object[] { Long.valueOf(duration),
                            requestString, detail }));
                } else if (perf.isInfoEnabled()) {
                    //响应时间20ms以下，不打印详细信息，减少日志大小
                    perf.info(MessageFormat.format("|Y|{0,number}|{1}", new Object[] { Long.valueOf(duration),
                            requestString }));
                    bizLogger.info(MessageFormat.format("|Y|{0,number}|{1}", new Object[] { Long.valueOf(duration),
                            requestString }));
                }

                Profiler.reset();
            }
        }

    }

    /**
     * 判断是否包含静态资源
     * 
     * @param uri
     * @return
     */
    private boolean containExtenSuffix(String uri) {
        for (String suffix : STATIC_RESOUCES_SUFFIX) {
            if (uri.endsWith(suffix)) {
                return Boolean.TRUE;
            }
        }

        return Boolean.FALSE;
    }

    /**
     * 取得请求内容.
     * 
     * @param request
     * @return
     */
    private String dumpQueryString(HttpServletRequest request) {
        StringBuilder sb = new StringBuilder();
        sb.append(request.getMethod());
        sb.append("|").append(request.getRequestURI());

        String queryString = request.getQueryString();
        if (!Strings.isNullOrEmpty(queryString)) {
            sb.append("?").append(queryString);
        }
        return sb.toString();
    }

    /** 
     * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
     */
    @Override
    public void init(FilterConfig arg0) throws ServletException {
        this.setConfig(arg0);
    }

    /**
     * Getter method for property <tt>config</tt>.
     * 
     * @return property value of config
     */
    public FilterConfig getConfig() {
        return config;
    }

    /**
     * Setter method for property <tt>config</tt>.
     * 
     * @param config value to be assigned to property config
     */
    public void setConfig(FilterConfig config) {
        this.config = config;
    }

}